#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/sysinfo.h>
#include <execinfo.h>

#define __USE_GNU
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>

#include "data_handle_lib.h"
#include "log.h"

#define LOG_TAG "read_write_alt.c :"
#define MB_SIZE (1024 * 1024)
#define KB_SIZE 1024
#define  TEST_PORT_NUM  14//(ENMUM_RESERVE / 2)

static char *g_ptr[TEST_PORT_NUM];
static char *g_ptr_w[TEST_PORT_NUM];
static pthread_t rcv_data_thread[TEST_PORT_NUM];
static pthread_t snd_data_thread[TEST_PORT_NUM];
static pthread_barrier_t barrier;
static int pthread_run_flag = 1;

int get_sysinfo(void)
{
    struct sysinfo info;
    int ret_val = -1, online_cpu_cores = 0;

    ret_val = sysinfo(&info);
    if (ret_val < 0)
    {
        LOGE(LOG_TAG"sysinfo failed,errno = %d\r\n", errno);
        ret_val = -errno;
    }

    online_cpu_cores = sysconf(_SC_NPROCESSORS_ONLN);
    LOG("system cpu num is %ld\n", sysconf(_SC_NPROCESSORS_CONF));
    LOG("system enable cpu(online cpu) num is %d\n", online_cpu_cores);

    LOG("ret_val         : %d\n", ret_val);
    LOG("uptime          : %ld\n", info.uptime);
    LOG("1 min load average : %.2f%%\n", info.loads[0] / 65536.0 / online_cpu_cores); // info.loads[0] / 65536.0 / cores_of_cpu  we can get the percentage of cpu load
    LOG("5 min load average : %.2f%%\n", info.loads[1] / 65536.0 / online_cpu_cores);
    LOG("15 min load average: %.2f%%\n", info.loads[2] / 65536.0 / online_cpu_cores);
    LOG("totalram           : %lu\n", info.totalram / MB_SIZE);
    LOG("freeram            : %lu\n", info.freeram / MB_SIZE);
    LOG("procs              : %u\n", info.procs);
    return 0;
}

int pthread_create_and_setaffinity(pthread_t *thread, const pthread_attr_t *attr,
                                   void *(*start_routine)(void *), void *arg, int affinity_core_num)
//affinity_core_num 0--> do not set affinity,(>0)--> set affinity affinity_core_num-1
{
    int ret_val = -1, online_cpu_cores = 0;
    static int first_round = 0;
    cpu_set_t cpu_info;

    if (!first_round)
    {
        get_sysinfo();
        first_round = 1;
    }

    ret_val = pthread_create(thread, attr, start_routine, arg);
    if (ret_val != 0)
    {
        LOGE(LOG_TAG"pthread_create faild,errno = %d!\r\n", errno);
        return -errno;
    }

    if (affinity_core_num >= 0)
    {
        online_cpu_cores = sysconf(_SC_NPROCESSORS_ONLN);
        if (affinity_core_num >= online_cpu_cores)
        {
            LOG("affinity_core_num %d,", affinity_core_num);
            affinity_core_num = affinity_core_num % online_cpu_cores;
            LOG("online cpu cores:%d,set affinity_core_num to %d\r\n", online_cpu_cores, affinity_core_num);
        }

        CPU_ZERO(&cpu_info);
        CPU_SET(affinity_core_num, &cpu_info);
        if (0 != pthread_setaffinity_np(*thread, sizeof(cpu_set_t), &cpu_info))
        {
            LOGE(LOG_TAG"pthread_setaffinity_np thread set affinity failed,errno = %d\r\n", errno);
            return -errno;
        }
    }
    return 0;
}

static int open_or_create_file(const char *file_path, int clean_file)
{
    int fd = -1, ret_val = -1;
    fd = open(file_path, O_RDWR | O_CREAT | O_LARGEFILE);
    if (fd < 0)
    {
        LOGE(LOG_TAG"open failed for file:%s,errno = %d\r\n", file_path, errno);
        return -errno;
    }

    if (clean_file)
    {
        ret_val = ftruncate(fd, 0);
        if (fd < 0)
        {
            LOGE(LOG_TAG"ftruncate failed for file:%s,errno = %d\r\n", file_path, errno);
            return -errno;
        }
    }

    return fd;
}

#define SND_LEN 16
static void *write_data_func(void *arg)
{
    int ret_val = -1, i = 0, src_fd = -1;
    data_blk_typedef *t_data_blk = (data_blk_typedef *)arg;
    unsigned int blk_type = t_data_blk->block_type;
    char file_path_array[100];

    pthread_barrier_wait(&barrier);
    LOGD("write_data_func[%d] blkptr : %p,thread id = %lu\r\n", t_data_blk->block_type, t_data_blk[i].block_ptr, pthread_self());

    memset(file_path_array, '\0', sizeof(file_path_array));
    snprintf(file_path_array, sizeof(file_path_array), "src_data_to_read_%d", blk_type);

    src_fd = open_or_create_file(file_path_array, 0);
    if (src_fd < 0)
    {
        LOGE(LOG_TAG"open failed for file:%s,errno = %d\r\n", file_path_array, errno);
        exit(1);
    }

    while (pthread_run_flag)
    {
        memset(t_data_blk->block_ptr, '\0', SND_LEN);
        ret_val = read(src_fd, t_data_blk->block_ptr, SND_LEN);
        if (ret_val <= 0)
        {
            if (ret_val == 0)
                LOGD(LOG_TAG"%s:end of file\r\n", file_path_array);
            else
                LOGE(LOG_TAG"read src file:%s failed,errno = %d\r\n", file_path_array, errno);
            break;
        }
        //LOGD("write %d data,port %d\r\n",ret_val,blk_type);
        t_data_blk->block_type = blk_type;
        t_data_blk->block_size = ret_val;
        ret_val = write_data(t_data_blk);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"write_data failed,errno = %d\r\n", errno);
            break;
        }
        usleep(10000);
    }
    close(src_fd);
}
#define STORE_DATA
static void *read_data_func(void *arg)
{
    int ret_val = -1, i = 0, store_fd = -1;
    data_blk_typedef *t_data_blk = (data_blk_typedef *)arg;
    unsigned int blk_type = t_data_blk->block_type;
    unsigned int blk_size = t_data_blk->block_size;
    char file_path_array[100];
    unsigned long long int pack_count = 0;

    pthread_barrier_wait(&barrier);
    //LOGD(LOG_TAG"t_data_blk[%d]: %p,t_data_blk.block_ptr:%p\r\n",t_data_blk->block_type,(void *)t_data_blk,t_data_blk->block_ptr);
    //LOGD(LOG_TAG"enter read_data_func,t_data_blk.block_type = %d\r\n", t_data_blk->block_type);
    LOGD("read_data_func[%d] blkptr : %p,thread id = %lu\r\n", t_data_blk->block_type, t_data_blk[i].block_ptr, pthread_self());
#ifdef STORE_DATA
    memset(file_path_array, '\0', sizeof(file_path_array));
    snprintf(file_path_array, sizeof(file_path_array), "dst_data_to_store_%d", blk_type);

    store_fd = open_or_create_file(file_path_array, 1);
    if (store_fd < 0)
    {
        LOGE(LOG_TAG"open failed for file:%s,errno = %d\r\n", file_path_array, errno);
        exit(1);
    }
#endif
    while (pthread_run_flag)
    {
        t_data_blk->block_type = blk_type;
        t_data_blk->block_size = blk_size;
        ret_val = read_data(t_data_blk);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"read_data failed,errno = %d\r\n", errno);
            break;
        }
        pack_count++;
        LOGD(LOG_TAG"rcv %d data,t_data_blk.block_type : 0x%x,0x%x,thread id = %lu,pack_count[%d] = %lu\r\n", ret_val, t_data_blk->block_type, blk_type, pthread_self(),blk_type,pack_count);
#ifdef STORE_DATA
        ret_val = write(store_fd, t_data_blk->block_ptr, t_data_blk->block_size);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"write failed for file:%s,errno = %d\r\n", file_path_array, errno);
            break;
        }
#endif
#if 0
        {
            LOG("rcv data:\r\n");
            for (i = 0; i < ret_val; i++)
            {
                if (i > 0 && i % 8 == 0)
                    LOG("\r\n");
                LOG("%02x  ", t_data_blk->block_ptr[i]);
            }
            LOG("\r\n");
        }
#endif
    }
#ifdef STORE_DATA
    close(store_fd);
#endif
}

static void sigfunc(int sig)
{
    int i = 0;
    LOGD(LOG_TAG"recv a sig = %d,do clean flow!\r\n", sig);
    transfer_stop();
    pthread_run_flag = 0;
    env_deinitial();

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        //pthread_join(rcv_data_thread[i], NULL);
        pthread_cancel(rcv_data_thread[i]);
        rcv_data_thread[i] = -1;
        pthread_cancel(snd_data_thread[i]);
        snd_data_thread[i] = -1;
    }

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        if (g_ptr[i])
        {
            free(g_ptr[i]);
            g_ptr[i] = NULL;
        }

        if (g_ptr_w[i])
        {
            free(g_ptr_w[i]);
            g_ptr_w[i] = NULL;
        }
    }
    LOGD(LOG_TAG"free mem done!\r\n");
    LOGD(LOG_TAG"terminal the programme!\r\n");
    exit(1);
}


#define BACKTRACE_SIZE   32
static void dump(void)
{
    int j, nptrs;
    void *buffer[BACKTRACE_SIZE];
    char **strings;
    LOGD(LOG_TAG"dump func,errno = %d\r\n", errno);
    nptrs = backtrace(buffer, BACKTRACE_SIZE);

    LOG("backtrace() returned %d addresses\n", nptrs);

    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL)
    {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (j = 0; j < nptrs; j++)
        LOG("  [%02d] %s\n", j, strings[j]);
    LOGD("free strings %s,%d\r\n", __func__, __LINE__);
    if (strings)
        free(strings);
}


static void seg_fault_sigfunc(int sig)
{
    int i = 0;
    LOGD(LOG_TAG"recv a sig = %d,do clean flow!\r\n", sig);

    LOGD(LOG_TAG"Dump stack start...\n");
    dump();
    LOGD(LOG_TAG"Dump stack end...\n");

    signal(sig, SIG_DFL);
    raise(sig);

    transfer_stop();
    pthread_run_flag = 0;
    env_deinitial();

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        //pthread_join(rcv_data_thread[i], NULL);
        pthread_cancel(rcv_data_thread[i]);
        rcv_data_thread[i] = -1;
        pthread_cancel(snd_data_thread[i]);
        snd_data_thread[i] = -1;
    }

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        if (g_ptr[i])
        {

            free(g_ptr[i]);
            g_ptr[i] = NULL;
        }

        if (g_ptr_w[i])
        {
            free(g_ptr_w[i]);
            g_ptr_w[i] = NULL;
        }
    }
    LOGD(LOG_TAG"free mem done!\r\n");
    LOGD(LOG_TAG"terminal the programme!\r\n");
    exit(1);
}

int main(int argc, char **argv)
{
    int ret_val = -1, i = 0, j = 0;
    int dma_ch_number = 1, user_ch_number = 1;
    int block_num = 80, block_size = 1024;
    char *ptr = NULL;
    data_blk_typedef t_data_blk[TEST_PORT_NUM];
    data_blk_typedef t_data_blk_w[TEST_PORT_NUM];
    data_blk_typedef t_data_blk_set_baud;
    unsigned int baud_rate;
    sig_t sighandler;

    if (argc < 5)
    {
        LOG("usage:%s dma_ch_number user_ch_number block_num block_size\r\n",argv[0]);
        return 0;
    }

    if (argv[1])    dma_ch_number = atoi(argv[1]);
    else            dma_ch_number = 1;
    if (argv[2])    user_ch_number = atoi(argv[2]);
    else            user_ch_number = 1;
    if (argv[3])    block_num = atoi(argv[3]);
    else            block_num = 5;
    if (argv[4])    block_size = atoi(argv[4]);
    else            block_size = 1024;

    LOGD(LOG_TAG"dma_ch_number = %d,user_ch_number = %d,block_number = %d,block_size = %d\r\n",dma_ch_number,user_ch_number,block_num, block_size);
    if (!block_num || !block_size)
    {
        LOGE(LOG_TAG"wrong argument block_num:%d(!=0),block_size:%d(!=0)\r\n", block_num, block_size);
        return 0;
    }

    if (pthread_barrier_init(&barrier, NULL, 2 * TEST_PORT_NUM + 1))
    {
        LOGE(LOG_TAG"pthread_barrier_init failed,errno = %d,exit", errno);
        return -errno;
    }

    sighandler = signal(SIGINT, sigfunc);
    if (sighandler == SIG_ERR)
    {
        LOGE(LOG_TAG"signal error!\r\n");
        return -errno;
    }

    sighandler = signal(SIGSEGV, seg_fault_sigfunc);
    if (sighandler == SIG_ERR)
    {
        LOGE(LOG_TAG"signal error!\r\n");
        return -errno;
    }

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        g_ptr[i] = malloc(block_size * 1024);
        if (g_ptr[i] == NULL)   goto main_err_out;
        t_data_blk[i].block_ptr = (unsigned char *)g_ptr[i];
        LOGD("blkptr : %p,size : %d\r\n", t_data_blk[i].block_ptr, block_size * 1024);
        t_data_blk[i].block_size = block_size * 1024;
        t_data_blk[i].block_type = ENMUM_LOW_SPEED_IO_0 + i;

        g_ptr_w[i] = malloc(block_size * 1024);
        if (g_ptr_w[i] == NULL)   goto main_err_out;
        t_data_blk_w[i].block_ptr = (unsigned char *)g_ptr_w[i];
        LOGD("blkptr : %p,size : %d\r\n", t_data_blk_w[i].block_ptr, block_size * 1024);
        t_data_blk_w[i].block_size = block_size * 1024;
        t_data_blk_w[i].block_type = ENMUM_LOW_SPEED_IO_0 + i;
    }
    ret_val = env_initial(dma_ch_number,user_ch_number,block_num, block_size);
    if (ret_val < 0)
    {
        LOGE(LOG_TAG"env_initial failed,errno = %d\r\n", errno);
        return -errno;
    }
    //set baud_rate
    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        baud_rate = BAUD_RATE(50);
        t_data_blk_set_baud.block_type = ENMUM_LOW_SPEED_IO_0 + i;
        t_data_blk_set_baud.block_size = sizeof(baud_rate);
        t_data_blk_set_baud.block_ptr = (unsigned char *)(&baud_rate);
        interface_initial(&t_data_blk_set_baud);
    }

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        ret_val = pthread_create_and_setaffinity(&rcv_data_thread[i], NULL, read_data_func, &t_data_blk[i], 6 + i);
        if (ret_val != 0)
        {
            LOGE(LOG_TAG"pthread_create_and_setaffinity faild for port_read_func,errno = %d!\r\n", errno);
            goto main_err_out;
        }
        LOGD(LOG_TAG"&t_data_blk[%d]: %p,t_data_blk[%d].block_ptr %p\r\n", i, &t_data_blk[i], i, t_data_blk[i].block_ptr);

        ret_val = pthread_create_and_setaffinity(&snd_data_thread[i], NULL, write_data_func, &t_data_blk_w[i], 6 + i);
        if (ret_val != 0)
        {
            LOGE(LOG_TAG"pthread_create_and_setaffinity faild for port_write_func,errno = %d!\r\n", errno);
            goto main_err_out;
        }
        LOGD(LOG_TAG"&t_data_blk_w[%d]: %p,t_data_blk_w[%d].block_ptr %p\r\n", i, &t_data_blk_w[i], i, t_data_blk_w[i].block_ptr);

    }
    usleep(3000000);
    transfer_start();
    pthread_barrier_wait(&barrier);
    while (1)
    {
        usleep(500000);
    }

main_err_out:
    transfer_stop();
    pthread_run_flag = 0;
    env_deinitial();

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        pthread_join(rcv_data_thread[i], NULL);
        rcv_data_thread[i] = -1;
        pthread_join(snd_data_thread[i], NULL);
        snd_data_thread[i] = -1;
    }

    for (i = 0; i < TEST_PORT_NUM; i++)
    {
        if (g_ptr[i])
        {
            free(g_ptr[i]);
            g_ptr[i] = NULL;
        }
        if (g_ptr_w[i])
        {
            free(g_ptr_w[i]);
            g_ptr_w[i] = NULL;
        }
    }
    pthread_barrier_destroy(&barrier);
    LOGD(LOG_TAG"free mem done!\r\n");
    return 0;
}

//gcc -o test_read test_read.c -lpthread -ldata_handle_lib
